package com.example.lt.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.lt.constant.RedisConstant;
import com.example.lt.service.FeedService;
import com.example.lt.util.DateUtil;
import com.example.lt.util.RedisUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;

/**
 * @Author 小涛Tao
 * @Date: 2024/07/30/17:54
 * version 1.0 注释：收件箱和发件箱实现类
 * Redis 有序集合方法命名规则
 * Redis 有序集合的命名规则通常很直观：
 * zRange: 表示按范围获取元素，默认是按分数从低到高排序。
 * zRevRange: 表示按范围获取元素，反向排序，即按分数从高到低排序。
 * ByScore: 表示按分数范围获取元素。
 * WithScores: 表示返回的结果中包含元素及其分数。
 **/
@Service
@RequiredArgsConstructor // 创建有参构造方法注解
public class FeedServiceImpl implements FeedService {

    private final RedisTemplate redisTemplate;

    private final RedisUtil redisUtil;

    /**
     * 推入发件箱
     */
    @Override
    @Async
    public void pusOutBoxFeed(Long userId, Long videoId, Long time) {
        redisUtil.zadd(RedisConstant.OUT_FOLLOW + userId, time, videoId, -1);
    }

    /**
     * 删除发件箱
     * 当前用户删除视频时 调用 -> 删除【当前用户的发件箱中视频】以及【粉丝下的收件箱中的视频】
     */
    @Override
    @Async
    public void deleteOutBoxFeed(Long userId, Collection<Long> fans, Long videoId) {
        // 删除用户发件箱中的 videoId，删除用户粉丝收件箱中的 videoId
        final String fansIn = RedisConstant.IN_FOLLOW;
        final String outFollow = RedisConstant.OUT_FOLLOW;
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Long fan : fans) {
                connection.zRem((fansIn + fan).getBytes(), String.valueOf(videoId).getBytes());
            }
            connection.zRem((outFollow + userId).getBytes(), String.valueOf(videoId).getBytes());
            return null;
        });
    }

    /**
     * 删除收件箱，当用户取关用户时调用，->删除自己收件箱中的videoIds
     * @param userId 用户id
     * @param videoIds 关注人发的视频id集合
     */
    @Override
    @Async
    public void deleteInBoxFeed(Long userId, List<Long> videoIds) {
        redisTemplate.opsForZSet().remove(RedisConstant.IN_FOLLOW + userId, videoIds.toArray());
    }

    /**
     * 更新用户的收件箱
     * @param userId 用户id
     * @param followIds 用户关注了的id集合
     */
    @Override
    @Async
    public void initFollowFeed(Long userId, Collection<Long> followIds) {
        String inFollow = RedisConstant.IN_FOLLOW;
        final Date curDate = new Date();
        final Date limitDate = DateUtil.addDateDays(curDate, -7); // 得到 7 天前这个时候的时间

        // 有序集合中的元素升序排序，然后获取最后一个元素及其分数，就是获取创建时间是最新的（分数是最高的）
        final Set<ZSetOperations.TypedTuple<Object>> set = redisTemplate.opsForZSet().rangeWithScores(inFollow + userId, -1, -1);
        if (!ObjectUtils.isEmpty(set)) { // 用户收件箱中有元素的情况下，就从这最后一个元素的时间开始
            final Double oldTime = set.iterator().next().getScore(); // 获取用户收件箱中最新一个元素的时间（分数最高的元素）
            init(userId, oldTime.longValue(), (new Date()).getTime(), followIds);
        } else { // 用户收件箱中没有元素的情况下，就 7 天前的这个时候开始
            init(userId, limitDate.getTime(), curDate.getTime(), followIds);
        }
    }

    // 从关注人的发件箱中获取满足条件（min < ? < max）的元素及其对应分数，并加入到用户的收件箱中
    public void init(Long userId, Long min, Long max, Collection<Long> followIds) {
        final String outFollow = RedisConstant.OUT_FOLLOW;
        final String inFollow = RedisConstant.IN_FOLLOW;

        // 从每一个关注人的发件箱，获取在指定分数之间（min < ? < max）的集合中从偏移量 0 开始最多取 20 的元素及其对应分数，从高到低的排序方式
        final List<Set<DefaultTypedTuple>> result = redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Long followId : followIds) {
                connection.zRevRangeByScoreWithScores((outFollow + followId).getBytes(), min, max, 0, 20);
            }
            return null;
        });

        final ObjectMapper objectMapper = new ObjectMapper();
        final Set<Long> ids = new HashSet<>();
        // 遍历从关注人发件箱中获取的 tuple 集合，并将其元素和对应的分数加入收件箱
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (Set<DefaultTypedTuple> tuples : result) {
                if (!ObjectUtils.isEmpty(tuples)) {

                    for (DefaultTypedTuple tuple : tuples) {
                        // 将 tuple 加入收件箱
                        final Object value = tuple.getValue();
                        ids.add(Long.parseLong(value.toString()));
                        final byte[] key = (inFollow + userId).getBytes();
                        try {
                            connection.zAdd(key, tuple.getScore(), objectMapper.writeValueAsBytes(value));
                        } catch (JsonProcessingException e) {
                            e.printStackTrace();
                        }
                        connection.expire(key, RedisConstant.HISTORY_TIME);

                    }

                }
            }
            return null;
        });
    }

}
